
import java.io.IOException;
import java.io.OutputStream;
import java.io.ObjectOutputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.io.PrintStream;
import java.io.BufferedOutputStream;
import java.net.Socket;
import java.net.InetAddress;
import java.net.UnknownHostException;

class Host implements Serializable{

  //Socket to this host (if open)
  private Socket socket;

  //Canonical host name
  private String name;

  //host ip address
  private String ip;

  //host node name
  private String nodeName = "";

  //output stream to this host (if open)
  private OutputStream os;

  //input stream from this host (if open)
  private InputStream is;

  //object output stream to this host (if open)
  private ObjectOutputStream oos;

  //object input stream from this host (if open)
  private ObjectInputStream ois;

  Host(String hostId, String nodeName)
    throws HostException
  {
    setHostId(hostId);
    this.nodeName = nodeName;
    socket = null;
    os = null;
    is = null;
  }

  public Host(String hostId)
    throws HostException
  {
    setHostId(hostId);
  }

  private void setHostId(String hostId)
    throws HostException
  {
    InetAddress address;
    try {
      address = InetAddress.getByName(hostId);
    }catch (UnknownHostException e){
      throw new HostException("Nombre de host '" + hostId + "' inválido: " + e.getMessage());
    }

    ip = address.getHostAddress();
    name = address.getCanonicalHostName();
  }

  public void startRequest(int port)
    throws HostException
  {
    //Socket
    try{
      socket = new Socket(ip, port);
    }catch(IOException e){
      throw new HostException("Error conectándose para hacer solicitud: " + e.getMessage());
    }

    //Output stream for request
    try{
      os = socket.getOutputStream();
    }catch(IOException e){
      throw new HostException("Error obteniendo un OutputStream: " + e.getMessage());
    }

    //Output stream object for request
    try{
      oos = new ObjectOutputStream(os);
      oos.flush();
    }catch(IOException e){
      throw new HostException("Error obteniendo un objeto de OutputStream: " + e.getMessage());
    }

    //Input stream for response
    try{
      is = socket.getInputStream();
    }catch(IOException e){
      throw new HostException("No se pudo obtener el input stream para iniciar el request: " + e.getMessage());
    }

    //Input stream object for response
    try{
      ois = new ObjectInputStream(is);
    }catch(IOException e){
    e.printStackTrace();
      throw new HostException("No se pudo obtener el objeto input stream: " + e.getMessage());
    }

  }

  public Response request(Request request)
    throws HostException
  {
     return request(request, null);
  }
  public Response request(Request request, PrintStream out)
    throws HostException
  {

    //Write request to output stream
    try{
      //out.println("Buscando por '" + request.getCreator() + "' y '" + request.getTitle() + "'");
      oos.writeObject(request);
    }catch(IOException e){
      throw new HostException("Error enviando el objeto solicitud: " + is + e.getMessage());
    }

    //Read request object
    Response response;
    try{
      response = (Response) ois.readObject();
    }catch(IOException e){
      throw new HostException("No se pudo leer el objeto desde el input stream: " + e.getMessage());
    }catch(ClassNotFoundException e){
      throw new HostException("La clase para el objeto leído no está cargada: " + e.getMessage());
    }

    //Close input/output stream
    //try{
    //  oos.close();
    //  ois.close();
    //}catch(IOException e){
    //  throw new HostException("Error cerrando la conexión: " + e.getMessage());
    //}

    return response;
  }

  /**
   * Copies the information put into the open socket into the file pointed by 'file'
   * 
   * @param file Buffer to which the file should be downloaded
   */
  public void download(BufferedOutputStream file, long size) throws HostException{
    long available;
    // 1MB buffer to read from the socket. This ensures that the bytes read won't
    // exceed the permitted capcity
    byte buffer[] = new byte[1048576];
    int read;

    try{
        while(size > 0){
            read = is.read(buffer, 0, buffer.length);
            file.write(buffer, 0, read);
            size -= read;
        }
    }catch(IOException io){
        throw new HostException("Error en manejo de socket/buffer: " + io.getMessage());
    }
  }

  public void endRequest()
    throws HostException
  {

    //Close connection
    try{
      os.close();
      is.close();
      socket.close();
    }catch(IOException e){
      throw new HostException("Error cerrando la conexión: " + e.getMessage());
    }
    /*socket = null;
    os = null;
    is = null;*/
  }

  public String getNodeName(){
    return nodeName;
  }

  public String getHostName(){
    return name;
  }

  public boolean equals(Host h){
    return nodeName.equals(h.nodeName);
  }

  public String toString(){
    return "name: " + name + " ip: " + ip + " nodeName: " + nodeName;
  }
}